home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapmovevertex.py < prev    next >
Text File  |  2004-01-05  |  30KB  |  914 lines

  1. """   QuArK  -  Quake Army Knife
  2.  
  3. Vertex moveing plugin
  4. """
  5.  
  6. #
  7. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  8. # FOUND IN FILE "COPYING.TXT"
  9. #
  10.  
  11. # $Header: /cvsroot/quark/runtime/plugins/mapmovevertex.py,v 1.7 2002/05/18 22:38:31 tiglari Exp $
  12.  
  13. Info = {
  14.    "plug-in":       "Vertex Movement Dialog",
  15.    "desc":          "dialg for moving vertexes",
  16.    "date":          "22 Sep 2000",
  17.    "author":        "tiglari",
  18.    "author e-mail": "tiglari@hexenworld.net",
  19.    "quark":         "Version 6.x" }
  20.  
  21.  
  22. #
  23. # The import statements make material in other files available
  24. #  to the statements in this file
  25. #
  26.  
  27. #
  28. # Quarkx is the delphi-defined API
  29. #
  30. import quarkx
  31.  
  32. #
  33. # This imports the maphandles.py file in the quarkpy folder
  34. #  To use something from this file you need to `quality' its
  35. #  name with quarkpy.maphandles, e.g. `quarkpy.maphandles.VertexHandle`
  36. import quarkpy.maphandles
  37.  
  38. #
  39. # for face menu options
  40. #
  41. import quarkpy.mapoptions
  42.  
  43. #
  44. # This imports the tagging.py plugin in this folder
  45. #
  46. import tagging
  47.  
  48. #
  49. # This imports every function in quarkpy\maputils
  50. #  things imported in this way don't need to be (& in fact
  51. #  can't be) qualified
  52. #
  53. from quarkpy.maputils import *
  54.  
  55. #
  56. # For the stuff below about moving some object containing a vertex
  57. #
  58. from quarkpy import guiutils
  59.  
  60. #
  61. # repeated from maputils to make it work with older quark versions.
  62. #
  63.  
  64. def projectpointtoplane(p,n1,o2,n2):
  65.   "project point to plane at o2 with normal n2 along normal n1"
  66.   v1 = o2-p
  67.   v2 = v1*n2
  68.   v3 = n1*n2
  69.   v4 = v2/v3
  70.   v5 = v4*n1
  71.   return p + v5
  72.  
  73. #
  74. # A utility for accessing attributes of objects
  75. #  (class instances) that might not be defined
  76. #
  77. def getAttr(object, attr, default=None):
  78.     if hasattr(object, attr):
  79.         return getattr(object,attr)
  80.     else:
  81.         return default
  82. #
  83. # ditto for deleting attributes
  84. #
  85. def delAttr(object, attr):
  86.     if hasattr(object, attr):
  87.          delattr(object, attr)
  88.  
  89. def appendToAttr(object, attr, thing):
  90.     if hasattr(object, attr):
  91.         getattr(object,attr).append(thing)
  92.     else:
  93.         setattr(object,attr,[thing])
  94.  
  95. def removeFromAttr(object, attr, thing):
  96.     getattr(object,attr).remove(thing)
  97.     if getattr(object,attr)==[]:
  98.         delattr(object,attr)
  99. #
  100. # This is a definition of the vertex-movement dialog
  101. # The `live edit dialog' is a rather fancy critter defined
  102. #  in quarkpy/dlgclasses.  Don't worry about its innards,
  103. #  but you'll see how it's used further below.
  104. #
  105. # Read http://..adv.quarkpy.gui.html#dialogs in the infobase
  106. #  for details on how this stuff defines the appearance of
  107. #  dialog and the data-entry forms in it.
  108. #
  109. #  `EU` is the form with a field that you can enter a numerical
  110. #   value, and up-down buttons for increment/decrement
  111. #
  112. #  Somebody has really gotta gettaroundto documenting all the
  113. #   different Typ's...
  114. #
  115.  
  116. class VtxDragDlg(quarkpy.dlgclasses.LiveEditDlg):
  117.     #
  118.     # dialog layout
  119.     #
  120.  
  121.     endcolor = AQUA
  122.     size = (140,120)
  123.     dfsep = 0.50
  124.  
  125.     dlgdef = """
  126.         {
  127.             Style = "9"
  128.         Caption = "Vertex Movement"
  129.  
  130.         X: = 
  131.         {
  132.         Txt = "X"
  133.         Typ = "EU"
  134.         Hint = "X position"
  135.         }
  136.  
  137.         Y: = 
  138.         {
  139.         Txt = "Y"
  140.         Typ = "EU"
  141.         Hint = "Y position"
  142.         }
  143.  
  144.         Z: = 
  145.         {
  146.         Txt = "Z"
  147.         Typ = "EU"
  148.         Hint = "Z position"
  149.         }
  150.  
  151.         sep: = { Typ="S" Txt=" " }
  152.  
  153.         gridpos: =
  154.         {
  155.         Txt = "Grid Position"
  156.         Typ="X"
  157.         Hint="If this is checked, vertexes will move to grid positions"
  158.         }
  159.         
  160.         griddel: =
  161.         {
  162.         Txt = "Grid Delta"
  163.         Typ="X"
  164.         Hint="If this is checked, vertexes will move by grid increments" $0D " (overridden by Grid Position)"
  165.         }
  166.  
  167.         sloppy: =
  168.         {
  169.         Txt = "Sloppy"
  170.         Typ="X"
  171.         Hint="If this is checked, vertexes move in the plane of a locked face, but not necessarily to where you specify"
  172.         }
  173.  
  174.         sep: = { Typ="S" Txt=""}
  175.  
  176.         exit:py = {Txt="" }
  177.     }
  178.     """
  179.  
  180. #
  181. # Main math function.  Might shift into maputils.
  182. #
  183. def rotateFace(face, mvtx, delta, pivot, pivot2):
  184.     "rotates face from mvtx by delta around pivots"
  185. #    debug('delta: '+`delta`)
  186.     #
  187.     # Much more laborious than Tim Smith's maphandles
  188.     #  method, but seems to prevent `drifting' of supposedly
  189.     #  locked vertices from their locked positions
  190.     #
  191.     # The idea if first to use the pivots and
  192.     #  the new position as threepoints, then fix up
  193.     #  the texture by rotating the texture threepoints,
  194.     #  (code 3), and projecting them onto the new face.
  195.     #
  196.     # Probably a more streamlined approach will work, but
  197.     #  the drifting problem is a pain.
  198.     #
  199.     rotationAxis = (pivot2-pivot).normalized
  200.     norm1 = (rotationAxis^(mvtx-pivot)).normalized
  201.     plane1 = rotationAxis^norm1
  202.     norm2 = (rotationAxis^(mvtx+delta-pivot)).normalized
  203.     plane2 = rotationAxis^norm2
  204.     points = face.threepoints(3)
  205.     facenorm = face.normal
  206.     def proj(v, org=pivot, ax1=rotationAxis, ax2=plane1):
  207.         return (v-org)*ax1, (v-org)*ax2
  208.     #
  209.     # map applies proj to each member in the tuple `points',
  210.     #  producing the new tuple `pcoords'
  211.     #
  212.     pcoords = map(proj, points)
  213.     face.setthreepoints((pivot2, pivot, mvtx+delta),0)
  214.     if facenorm*face.normal<0:
  215.         face.swapsides()
  216.     newpoints = face.threepoints(0)
  217.     def proj2plane(p,pp=newpoints[1],nn=face.normal):
  218.         return projectpointtoplane(p,nn,pp,nn)
  219.     def unproj((x, y),org=pivot,ax1=rotationAxis, ax2=plane2):
  220.         return org+x*ax1+y*ax2
  221.     #
  222.     # Well I could have piled up three maps, and not
  223.     #  used the pcoords variable, but I didn't!
  224.     #
  225.     newpoints = tuple(map(proj2plane, (map(unproj, pcoords))))
  226.     face.setthreepoints(newpoints,3)
  227.     
  228.  
  229. #
  230. # Here's the function that does the real work of moving the faces.
  231. # It's a good idea to separate out the functions & classes that
  232. #  perform significant work on the data being manipulated from
  233. #  those that are basically oriented towards the UI
  234. #
  235. def moveFaces(faces, mvtx, delta, poly, locklist, freezelist, sloppy=None):
  236.     "returns the moving old, new faces, of poly , with mvtx moved by delta"
  237.     result = []
  238.     old = []
  239.     for face in faces:
  240.         if face in freezelist:
  241.             continue
  242.         newface = face.copy()
  243.         norm = face.normal
  244.         facelocked = []
  245.         #
  246.         # regular index method doesn't work for lists of vectors
  247.         #
  248.         vtxes = face.verticesof(poly)
  249.         vtxindex = 0
  250.         for vtx in vtxes:
  251.             if not(vtx-mvtx):
  252.                 moveindex = vtxindex   
  253.             for vtxh in locklist:
  254.                 #
  255.                 # Testing for closeness rather than full-precision
  256.                 #  superposition seems to be necessary to keep the
  257.                 #  locked vertices from coming adrift.  There's
  258.                 #  no theory behind the choice of value; it just
  259.                 #  seems to work.
  260.                 #
  261.                 if (vtxh.poly is poly and abs(vtxh.pos-vtx)<.1):
  262.                     facelocked.append(vtxh.pos)
  263.                     #
  264.                     # we only use this if only one vertex is locked
  265.                     #
  266.                     lockindex=vtxindex
  267.                 vtxindex=vtxindex+1
  268.         if len(facelocked) == 0:
  269.             proj = delta*norm
  270.             newface.translate(proj*norm)
  271. #            debug('translated '+newface.shortname)    
  272.         if len(facelocked) == 1:
  273.             #
  274.             # this autolock stuff isn't working right so its
  275.             #  dialog checkbox is commented out to disableit
  276.             #
  277.             if 0:
  278. #            if autolock:
  279.                 cyclelength = len(vtxes)
  280.                 if cyclelength/abs(moveindex-lockindex)!=2:
  281.                     #
  282.                     # I think a mathematician could do this better
  283.                     #
  284.                     halfcycle = cyclelength/2.0
  285.                     if moveindex<lockindex:
  286.                         if lockindex-moveindex<halfcycle:
  287.                             secondlockindex=lockindex+1
  288.                         else:
  289.                             secondlockindex=lockindex-1
  290.                     elif lockindex<moveindex:
  291.                         if moveindex-lockindex<halfcycle:
  292.                             secondlockindex=lockindex-1
  293.                         else:
  294.                             secondlockindex=lockindex+1
  295.                     #
  296.                     # the remainder operation is the go for cyclic
  297.                     #  indexing situations.
  298.                     #
  299.                     facelocked.append(vtxes[secondlockindex%cyclelength])
  300.         if len(facelocked)==1:        
  301.                 #
  302.                 # rotate around perp to line from mftx to pivot
  303.                 #
  304.                 pivot=facelocked[0]
  305.                 perp = newface.normal^(mvtx-pivot)
  306.                 rotateFace(newface, mvtx, delta, pivot, pivot+perp)
  307.         if len(facelocked) == 2:
  308.             rotateFace(newface, mvtx, delta, facelocked[0], facelocked[1])
  309. #            debug('2-rotated '+newface.shortname)
  310.         if len(facelocked) > 2:
  311.             if sloppy:
  312.                 continue
  313.             norm = face.normal
  314.             #
  315.             # Bail unless the new vertex position is on the
  316.             #  fully locked plane.
  317.             #
  318.             if norm*(mvtx+delta-facelocked[0]):
  319.                 return None, None
  320.         old.append(face)
  321.         result.append(newface)
  322.     return old, result
  323.  
  324. #
  325. # Now we get going on the interface.
  326. #
  327.  
  328. #
  329. # First define a new menu (see the plugins tutorial for the
  330. #  general method)
  331. #
  332. def vertexmenu(self, editor, view, oldmenu=quarkpy.maphandles.VertexHandle.menu.im_func):
  333.  
  334.     #
  335.     # This is a very convenient approach to defining the `callback'
  336.     #  functions that are called when a menu-item is clicked; the
  337.     #  default-argument specifications make it easy to pass all
  338.     #  sorts of info to the functions (e.g. self in the function
  339.     #  is equated to self in the environment).
  340.     # Won't work for callbacks that want to also be called from
  341.     #  the command menu or a toolbar button (like some of the ones
  342.     #  in maptagside)
  343.     #
  344.     def moveclick(n, self=self, editor=editor):
  345.          pos = self.pos
  346.          poly = self.poly
  347.          faces = poly.faces
  348.          #
  349.          # find out what faces are going to move
  350.          #
  351.          moving = []
  352.          for face in faces:
  353.              for vtx in face.verticesof(poly):
  354.                  #
  355.                  # Remember = doesn't work right for vectors;
  356.                  #  this is how you test for two vectors being
  357.                  #  equal
  358.                  #
  359.                  if not (vtx-pos):
  360.                     moving.append(face)
  361.                     continue
  362.     
  363.          #
  364.          # Now start setting up the live edit dialog; this is
  365.          #  a black magic process that you just follow
  366.          #
  367.          
  368.          #
  369.          #  The objects that the dialog is going to manipulate
  370.          #   are stuffed into a `pack' object
  371.          #
  372.          class pack:
  373.              "a place to stick stuff"
  374.          pack.poly = poly
  375.          pack.moving = moving
  376.          pack.pos = pos
  377.  
  378.          #
  379.          # Now define a setup function with `self' as its first
  380.          #  argument, and any number of further default arguments
  381.          #  specified to in stuff from above. 
  382.          #
  383.          # `self' refers the dialog, it's .src is the object
  384.          #  from which the displayed values in the dialog are
  385.          #  taken
  386.          #
  387.          def setup(self, pack=pack, editor=editor):
  388.              src = self.src
  389.              options = quarkx.setupsubset(SS_MAP, "Options")
  390.              src["gridpos"]=options["movevertex_gridpos"]
  391.              src["griddel"]=options["movevertex_griddel"]
  392.              src["sloppy"]=options["movevertex_sloppy"]
  393.              pos = pack.pos
  394.              #
  395.              # Tell the editor the position of the moving vertex,
  396.              #   so that a green box can be drawn around it
  397.              #
  398.              editor.movingvertex = pos
  399.              editor.invalidateviews()
  400.              #
  401.              # this code loads the x, y and z coordinates,
  402.              #  represented as strings, into the X, Y and Z
  403.              #  specifics of src, and hence ultimately into
  404.              #  the corresponding controls of the dialog
  405.              #
  406.              self.src["X"] = "%f"%pos.x
  407.              self.src["Y"] = "%f"%pos.y
  408.              self.src["Z"] = "%f"%pos.z
  409.              
  410.          #
  411.          # `action' is the function that is called when the
  412.          #   data in the dialog box is changed
  413.          #
  414.          def action(self, pack=pack, editor=editor):
  415.              #
  416.              # read the new data from self.src
  417.              #
  418.              src = self.src
  419.              #
  420.              # Showing off with lambda as well as map. Could be
  421.              # done three statements, in the style of setup above
  422.              #
  423.              x,y,z = map(lambda d,src=src:eval(src[d]),("X","Y","Z"))
  424.              newpos = quarkx.vect(x,y,z)
  425.              #
  426.              # calculate the desired movement, get list of locked
  427.              #  vertices, etc.
  428.              #
  429.              delta = newpos-pack.pos
  430.              #
  431.              # grid stuff
  432.              #
  433.              gridstep = editor.gridstep
  434.              #
  435.              # There's tricky approximation stuff here, just empirical,
  436.              #  I don't understand the math.
  437.              #
  438.              if gridstep:
  439.                  if src["gridpos"]:
  440.                      #
  441.                      # map a delta component
  442.                      #
  443.                      def delcom(d, p, gridstep=gridstep):
  444.                          offgrid = p%gridstep
  445.                          #
  446.                          # or should we force to grid?
  447.                          #
  448.                          if abs(d)<0.01:
  449.                             return 0.0
  450.                          #
  451.                          # little change, increment/decrement
  452.                          #
  453.                          if abs(d)<=1.0:
  454.                              if d>0:
  455.                                  return gridstep-offgrid
  456.                              else:
  457.                                  return -gridstep-offgrid
  458.                          #
  459.                          # otherwise round off change so final
  460.                          #  pozzie goes to nearest gridpoint
  461.                          #
  462.                          offgrid = (p+d)%gridstep
  463.                          if offgrid<(gridstep/2.0):
  464.                              return d-offgrid
  465.                          else:
  466.                              return d+gridstep-offgrid
  467.                  elif src["griddel"]:
  468. #                     debug('griddel')
  469.                      def delcom(d, p, gridstep=gridstep):
  470.                          if abs(d)<.01:
  471.                              return 0.0
  472.                          if abs(d)<=1.0:
  473.                              if d>0:
  474.                                  return gridstep
  475.                              else:
  476.                                  return -gridstep
  477.                          offgrid = d%gridstep
  478.                          if offgrid<(gridstep/2.0):
  479.                              return d-offgrid
  480.                          else:
  481.                              return d+gridstep-offgrid
  482.                  else:
  483.                      def delcom(d, p, gridstep=gridstep):
  484.                          return d
  485. #                 debug('orig delta: '+`delta`)
  486.                  delta = quarkx.vect(tuple(map(delcom,delta.tuple, pack.pos.tuple)))
  487.              oldfaces=pack.moving
  488.              locklist = getAttr(editor,'lockedVertices',[])
  489.              freezelist = getAttr(editor,'frozenFaces',[])
  490.              #
  491.              # calculate the new faces
  492.              #
  493.              oldfaces, newfaces = moveFaces(oldfaces, pack.pos, delta, pack.poly, locklist, freezelist, src["sloppy"])
  494.              #
  495.              # Mere return from function produces None return value.
  496.              #  exceptions would be the way to go if there were
  497.              #  more different kinds of failure to deal with.
  498.              #
  499.              # A `computational' routine such as moveFaces shouldn't
  500.              #  throw up error dialog boxes itself, but leave this
  501.              #  to the callers.
  502.              #
  503.              if newfaces is None:
  504.                  quarkx.msgbox("Face can't move because of locks",
  505.                      MT_ERROR, MB_OK)
  506.                  return
  507.              #
  508.              # swap them into the map
  509.              #
  510.              undo=quarkx.action()
  511.              for i in range(len(oldfaces)):
  512.                 undo.exchange(oldfaces[i], newfaces[i])
  513.              editor.ok(undo, "Move vertex")
  514.              #
  515.              # check for lost vertex
  516.              #
  517.              newpos = pack.pos+delta
  518.              if not freezelist:
  519.                for face in newfaces:
  520.                  for vtx in face.verticesof(pack.poly):
  521.                      if abs(newpos-vtx)<.01:
  522.                          break
  523.                  else:
  524.                      #
  525.                      # For some unknown magical reason, the appearance
  526.                      #  of this dialog causes the mov
  527.                      #
  528.                      if not src["sloppy"]:
  529.                          quarkx.msgbox("Moving Vertex Lost",MT_INFORMATION,MB_OK)
  530.                  break
  531.                  
  532.              #
  533.              # Now put the replacement info into pack (if
  534.              #  this isn't done, undo won't work right for
  535.              #  repeat entry of data info dialog
  536.              #
  537.              pack.moving = newfaces
  538.              pack.pos = newpos
  539.              #
  540.              # For some unkoown reason, the exchange operation
  541.              #  above causes the new stuff to become the selection;
  542.              #  so we set things back the way they were
  543.              #
  544.              editor.layout.explorer.sellist=[pack.poly]
  545.              #
  546.              # and reset the options in case we've changed them
  547.              #
  548.              options = quarkx.setupsubset(SS_MAP, "Options")
  549.              options["movevertex_gridpos"]=self.src["gridpos"]
  550.              options["movevertex_griddel"]=self.src["griddel"]
  551.              options["movevertex_sloppy"]=self.src["sloppy"]
  552.              
  553.          #
  554.          # This is an optional callback for final cleanup
  555.          #
  556.          def onclosing(self, editor=editor):
  557.              delAttr(editor,'movingvertex')
  558.              editor.invalidateviews()
  559.          
  560.          #
  561.          # And at least create the dialog.
  562.          # quarkx.clickform is the window last clicked in,
  563.          #  needed here by the rules of Black Magic
  564.          # 'vtxdrag' is the tag under which the dimensions
  565.          #  of the box are retained in setup.qrk (value of
  566.          #  dlgdim_vtxdrag specific); to check the size that
  567.          #  the dialog definition above is producing (size),
  568.          #  delete this from setup.qrk after closing quark,
  569.          #  and then reopen & fire up the dialog.
  570.          # Then editor is required, and the first two `callback'
  571.          #  functions we've defined, and the last as an option.
  572.          #
  573.          VtxDragDlg(quarkx.clickform, 'vtxdrag', editor, setup, action, onclosing)
  574.              
  575.     #
  576.     # And now a bunch of simple litte menu item callbacks
  577.     #
  578.     def lockclick(m, self=self, editor=editor):
  579.         try:
  580.             editor.lockedVertices.append(self)
  581.         except (AttributeError):
  582.             editor.lockedVertices = [self]
  583.         #
  584.         # redraw the views to see the effect.
  585.         #
  586.         editor.invalidateviews()
  587.         
  588.     def clearclick(m, self=self, editor=editor):
  589.         delAttr(editor,'lockedVertices')
  590.         delAttr(editor,'frozenFaces')
  591.         editor.invalidateviews()
  592.     
  593.     def unlockclick(m, editor=editor):
  594.         removeFromAttr(editor,'lockedVertices',m.unlockMe)
  595. #        editor.lockedVertices.remove(m.unlockMe)
  596. #        if editor.lockedVertices == []:
  597. #            del editor.lockedVertices
  598.         editor.invalidateviews()
  599.  
  600.  
  601.     #
  602.     # Moving a containing group so that the vertex shifts to ongrid
  603.     #
  604.     def moveparentpopup(self=self,editor=editor):
  605.         shift=self.pos-editor.aligntogrid(self.pos)
  606.  
  607.         def makeitem(object,editor,r,shift=shift):
  608.  
  609.             def Shifter(m, object=object, editor=editor, shift=shift):
  610.                 new=object.copy()
  611.                 undo=quarkx.action()
  612.                 new.translate(shift)
  613.                 undo.exchange(object, new)
  614.                 editor.ok(undo, "Move object")
  615.                 editor.invalidateviews()
  616.  
  617.             item = qmenu.item(object.name,Shifter, "Move me")
  618.             item.menuicon = object.geticon(1)
  619.             return item
  620.             
  621.         poly=self.poly
  622.         list=guiutils.buildParentPopupList(self.poly,makeitem, editor)
  623.         return list
  624.      
  625.     #
  626.     # And their menu items
  627.     #
  628.     moveparentitem = qmenu.popup("&Move Containing", moveparentpopup(),
  629.       hint="|The clicked-on containing object of this vertex will be moved so as to force the vertex onto the grid.")
  630.     moveitem = qmenu.item("&Move Vertex", moveclick)
  631.     lockitem = qmenu.item("&Lock Vertex", lockclick)
  632.     unlockitem = qmenu.item("&Unlock Vertex", unlockclick)
  633.     clearitem = qmenu.item("&Clear Locks", clearclick)
  634.     #
  635.     # And a bit of convoluted logic for enable/disable
  636.     #
  637.     locker = lockitem
  638.     try:
  639.         locklist = editor.lockedVertices
  640.     except (AttributeError):
  641.         pass
  642.     else:
  643.         for vtxh in locklist:
  644.             if not(vtxh.pos-self.pos):
  645.                 unlockitem.unlockMe = vtxh
  646.                 locker = unlockitem
  647.                 moveitem.state = qmenu.disabled
  648.                 break
  649.     #
  650.     # And we return the menu, the new items stuck onto the
  651.     #  front of the old.
  652.     #
  653.     return [moveparentitem, moveitem, locker, clearitem]+oldmenu(self,editor,view)
  654.     
  655. #
  656. # And here's the magic bit; when vertexmenu got defined, the
  657. #  original menu function for quarkpy.maphandles.VertexHandle
  658. #  got stored within it (as `oldmenu'); now we replace
  659. #  it in the quarkpy.maphandles.VertexHandle with out new
  660. #  creation
  661. #
  662. quarkpy.maphandles.VertexHandle.menu = vertexmenu
  663.  
  664. #
  665. # Should be generalized from tagging
  666. #
  667.  
  668. #
  669. # And a different version of the same trick to get the little
  670. #  blue squares drawn around the locked vertices, this time by
  671. #  replacing quarkpy.qbaseeditor.BaseEditor.finishdrawing
  672. #
  673. # Also the green square around the moving vertex
  674. #
  675. from tagging import drawredface # misnamed, oh well
  676.  
  677. def lockfinishdrawing(editor, view, oldmore=quarkpy.qbaseeditor.BaseEditor.finishdrawing):
  678.       cv = view.canvas()
  679.       try:
  680.          locks = editor.lockedVertices
  681.       except (AttributeError):
  682.          pass
  683.       else:
  684.          cv.pencolor = MapColor("Duplicator")
  685.          for vtxh in locks: # these are handles
  686.              p1 = view.proj(vtxh.pos)
  687.              tagging.drawsquare(cv,p1,8)
  688.       try:
  689.          moving = editor.movingvertex
  690.          p1 = view.proj(moving)
  691.       except (AttributeError):
  692.          pass
  693.       else:
  694.          if view.info["type"] == "3D":
  695.               scalefactor = 50
  696.          else:
  697.              scalefactor = 30
  698.          scale = view.scale(moving)
  699.          for (color, axis) in (MapColor("Tag"), (1,0,0)), (MapColor("Bezier"), (0,1,0)),(MapColor("Duplicator"), (0,0,1)):
  700.              cv.pencolor = color
  701.              p0 = view.proj(moving)
  702.              p1 = view.proj(moving+(scalefactor/scale)*quarkx.vect(axis))
  703.              cv.line(p0, p1)
  704.                  
  705. #         else:
  706. #             cv.pencolor = MapColor("Bezier")
  707. #             tagging.drawsquare(cv,p1,8)
  708.               
  709.  
  710.       cv.pencolor=MapColor("Duplicator")
  711.       try:
  712.           for face in editor.frozenFaces:
  713.               drawredface(view,cv,face)
  714.       except (AttributeError):
  715.           pass
  716.       
  717.       oldmore(editor, view)
  718.  
  719. quarkpy.qbaseeditor.BaseEditor.finishdrawing = lockfinishdrawing
  720.  
  721.  
  722. def vec2rads(v):
  723.     "returns pitch, yaw, in radians"
  724.     v = v.normalized
  725.     import math
  726.     pitch = -math.sin(v.z)
  727.     yaw = math.atan2(v.y, v.x)
  728.     return pitch, yaw
  729.  
  730. def find3DView(editor):
  731.     views = []
  732.     for v in editor.layout.views:
  733.         if v.info["type"]=="3D":
  734.             views.append(v)
  735.     if views == []:
  736.         return
  737.     return views[0]
  738.  
  739.  
  740. #
  741. # And now the whole bizness again to lock every vertex of a face
  742. #
  743. def lockfacemenu(o, editor, oldfacemenu = quarkpy.mapentities.FaceType.menu.im_func):
  744.     
  745.     def lockclick(m,face=o,editor=editor):
  746.         locks = getAttr(editor,'lockedVertices',[])
  747.         for poly in face.faceof:
  748.             for vtx in face.verticesof(poly):
  749.                 locks.append(quarkpy.maphandles.VertexHandle(vtx, poly))
  750.         editor.lockedVertices = locks
  751.         editor.invalidateviews()
  752.      
  753.     def unlockclick(m, face=o, editor=editor):
  754.         try:
  755.             locks = editor.lockedVertices
  756.         except (AttributeError):
  757.             return
  758.         for poly in face.faceof:
  759.             for vtx in face.verticesof(poly):
  760.                 for hvtx in locks[:]:
  761.                     if not (hvtx.pos-vtx):
  762.                         locks.remove(hvtx)
  763.         editor.invalidateviews()
  764.                         
  765.     def viewclick(m, face=o, editor=editor):
  766.         if quarkx.keydown('\020')==1: # shift is down
  767.             reverse = 1
  768.         else:
  769.             reverse = 0
  770.         clickview = quarkx.clickform.focus 
  771.         #
  772.         # clickform doesn't seem to work for floating 3d windows
  773.         #  so we just take the first.
  774.         #
  775.         if clickview is not None and clickview.info["type"]=="3D":
  776.             view = clickview
  777.         else:
  778.             view = find3DView(editor)
  779.             if view is None:
  780.                 quarkx.msgbox("Need an open 3D view for this one!",
  781.                    MT_ERROR,MB_OK)
  782.                 return
  783.         #
  784.         # Should have a 3D view here
  785.         #
  786.         pos, yaw, pitch = view.cameraposition
  787.         dist = abs(pos - face.origin)
  788.         if reverse:
  789.             norm = -face.normal
  790.         else:
  791.             norm = face.normal
  792.         newpos = face.origin+dist*(norm)
  793.         pitch, yaw = vec2rads(-norm)
  794.         view.cameraposition = newpos, yaw, pitch
  795.         editor.invalidateviews()
  796.  
  797.     def freezeclick(m, face=o, editor=editor):
  798.         appendToAttr(editor, 'frozenFaces', face)
  799.         editor.invalidateviews()
  800.         
  801.     def unfreezeclick(m, face=o, editor=editor):
  802.         removeFromAttr(editor, 'frozenFaces', face)
  803.         editor.invalidateviews()
  804.  
  805.     lockitem = qmenu.item("Lock Vertices",lockclick,"Locks all vertices on this face")
  806.     unlockitem = qmenu.item("Unlock Vertices", unlockclick,"Unlocks vertices on this face")
  807.     freezeitem = qmenu.item("Freeze Face", freezeclick,"|Plane of face doesn't move, so vertices are confined to move within it. (So they don't go exactly where they're told to).")
  808.     unfreezeitem = qmenu.item("Unfreeze Face", unfreezeclick, "|To unfreeze all frozen faces, clear locks on vertex menu.")
  809.     freezer = freezeitem
  810.     try:
  811.         freezelist = editor.frozenFaces
  812.     except (AttributeError):
  813.         pass
  814.     else:
  815.         for face in freezelist:
  816.             if o is face:
  817.                 freezer = unfreezeitem
  818.                 break
  819.                 
  820.     viewitem = qmenu.item("Look At", viewclick, "|An open 3D view shifts to look at this face head on.\n (SHIFT to look at the face from the back)")
  821.  
  822.     locking = qmenu.popup("Lock/Freeze",[lockitem,unlockitem, freezer],
  823.         hint = "|Submenu for items that are supposed to help in placing vertices by controlling which faces & vertices can move and which can't.")
  824.  
  825.     return [locking, viewitem]+oldfacemenu(o, editor)
  826.  
  827. quarkpy.mapentities.FaceType.menu = lockfacemenu
  828.  
  829.  
  830. #
  831. #  Code for looking at center of selection
  832. #
  833. def originmenu(self, editor, view, oldoriginmenu = quarkpy.qhandles.GenericHandle.OriginItems.im_func):
  834.   
  835.     menu = oldoriginmenu(self, editor, view)
  836.     
  837.     if view is not None:
  838.  
  839.       def seePointClick(m,self=self,editor=editor):
  840.         view = find3DView(editor)
  841.         pos, yaw, pitch = view.cameraposition
  842.         dir = (self.pos-pos).normalized
  843.         pitch, yaw = vec2rads(dir)
  844.         view.cameraposition = pos, yaw, pitch
  845.         editor.invalidateviews()
  846.  
  847.       seeitem = qmenu.item("Look To",seePointClick,"|Aims an open 3d view at object")
  848.     
  849.       menu[1:1] = [seeitem]
  850.  
  851.     return menu
  852.  
  853. quarkpy.qhandles.GenericHandle.OriginItems = originmenu
  854.  
  855.  
  856. #
  857. # Proper version of this now committed to main branch
  858. #
  859. #
  860. # 3d view camera circlestrafe
  861. #   This stuff is to be supported in qhandles as "C" mouse-drag
  862. #   option (Shift-LMB drag by default), so this code will
  863. #   be removed from the final version.
  864. #
  865.  
  866. def circleinit(self, editor, view, x, y, old = quarkpy.qhandles.SideStepDragObject.__init__):
  867.     self.editor = editor
  868.     old(self, editor, view, x, y)
  869.     
  870. quarkpy.qhandles.SideStepDragObject.__init__= circleinit
  871.     
  872. def circledragto(self, x, y, flags, olddragto=quarkpy.qhandles.SideStepDragObject.dragto):
  873.     sel = self.editor.layout.explorer.sellist
  874.     if sel and quarkx.keydown('Z')==1:
  875.         min, max = quarkx.boundingboxof(sel)
  876.         center = .5*(max+min)
  877.         pos, yaw, pitch = self.camerapos0
  878.         dist = abs(pos-center)
  879.         x = self.x0-x
  880.         y = self.y0-y
  881.         newdir = (pos + self.vleft*x + self.vtop*y - center).normalized
  882.         newpos = center+dist*newdir
  883.         pitch, yaw = vec2rads(-newdir)
  884.         self.view.animation = 1
  885.         self.view.cameraposition = newpos, yaw, pitch
  886.     else:
  887.         olddragto(self, x, y, flags)
  888.  
  889. quarkpy.qhandles.SideStepDragObject.dragto = circledragto
  890.  
  891. # $Log: mapmovevertex.py,v $
  892. # Revision 1.7  2002/05/18 22:38:31  tiglari
  893. # remove debug statement
  894. #
  895. # Revision 1.6  2002/04/01 08:30:05  tiglari
  896. # shift out the parentpopup menu stuff (to quarkpy.guiutils)
  897. #
  898. # Revision 1.5  2002/03/30 09:31:14  tiglari
  899. # Add 'move containing' item to the vertex RMB that moves a containing
  900. #   object so that the vertex becomes on-grid.
  901. #
  902. # Revision 1.4  2001/06/17 21:10:57  tiglari
  903. # fix button captions
  904. #
  905. # Revision 1.3  2001/06/16 03:19:05  tiglari
  906. # add Txt="" to separators that need it
  907. #
  908. # Revision 1.2  2001/05/24 22:28:05  tiglari
  909. # fix tuple bug, improved anti-drift
  910. #
  911. # Revision 1.1  2001/04/01 22:45:31  tiglari
  912. # initial commit
  913. #